
package cn.uncode.rpc.core;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import cn.uncode.rpc.common.CommonConstant;
import cn.uncode.rpc.exception.FrameworkException;




/**
 * <pre>
 * Desc a provider or a caller.
 * 所有获取URL的parameter时（即带参数的getXXX方法），都必须返回对象,避免不经意的修改引发错误，因为
 * 有些地方需要根据是否含这个参数来进行操作。
 * 
 * 对于getXXX，当不带defaultValue时，如果不存在就返回null
 * </pre>
 * 
 */

public class URL implements Comparable<URL>{
	
	public static final String PARAMTER_SEPARATOR = "\\&";
	public static final String PARAMTER_START_CHARACTER = "?";
	public static final String PARAMTER_EQUAL_SIGN = "=";
	public static final String PROTOCOL_SEPARATOR = "://";
	public static final String PATH_SEPARATOR = "/";
	public static final String PORT_SEPARATOR = ":";
	
	

    private String protocol;

    private String host;

    private int port;

    // interfaceName
    private String path;

    private Map<String, String> parameters;

    private volatile transient Map<String, Number> numbers;

    public URL(String protocol, String host, int port, String path) {
        this(protocol, host, port, path, new HashMap<String, String>());
    }

    public URL(String protocol, String host, int port, String path, Map<String, String> parameters) {
        this.protocol = protocol;
        this.host = host;
        this.port = port;
        this.path = path;
        if(parameters == null){
        	this.parameters = new HashMap<String, String>();
        }else{
        	this.parameters = parameters;
        }
        
    }
    
    
    /**
     * <pre>
     * url parsing
     * 示例：http://localhost:8081/user/login?n=1&p=2
     * 解析结果：
     * protocol:http
     * host:localhost
     * prot:8081
     * path:user/login
     * parameters:{n:1,p:2}
     * </pre>
     */
    public static URL valueOf(String url){
    	if(StringUtils.isBlank(url)){
    		throw new FrameworkException("url is null");
    	}
    	String protocol = null;
    	String host = null;
    	int prot = 0;
    	String path = null;
    	Map<String, String> parameters = new HashMap<String, String>();
    	int i = url.indexOf(PARAMTER_START_CHARACTER);
    	if(i >= 0){
    		String[] parts = url.substring(i+1).split(PARAMTER_SEPARATOR);
    		for(String part : parts){
    			part = part.trim();
    			if(part.length() > 0){
    				int j = part.indexOf(PARAMTER_EQUAL_SIGN);
    				if(j >= 0){
    					parameters.put(part.substring(0, j), part.substring(j + 1));
    				}else{
    					parameters.put(part, part);
    				}
    			}
    		}
    		url = url.substring(0, i);
    	}
    	i = url.indexOf(PROTOCOL_SEPARATOR);
    	if(i >= 0){
    		if(i ==0){
    			throw new FrameworkException("url messing protocol: \"" + url + "\"");
    		}
    		protocol = url.substring(0, i);
    		url = url.substring(i + 3);
    	}else{
    		i = url.indexOf(":/");
    		if(i >= 0){
        		if(i ==0){
        			throw new FrameworkException("url messing protocol: \"" + url + "\"");
        		}
        		protocol = url.substring(0, i);
        		url = url.substring(i + 3);
    		}
    	}
    	i = url.indexOf(PATH_SEPARATOR);
    	if(i >= 0){
    		path = url.substring(i + 1);
    		url = url.substring(0, i);
    	}
    	
    	i = url.indexOf(PORT_SEPARATOR);
    	if(i >= 0 && i < url.length() - 1){
    		prot = Integer.parseInt(url.substring(i + 1));
    		url = url.substring(0, i);
    	}
    	
    	if(url.length() > 0){
    		host = url;
    	}
    	
    	return new URL(protocol, host, prot, path, parameters);
    }
    
    
    /**
     * check if this url can serve the refUrl.
     *
     * @param refUrl
     * @return
     */
    public boolean canServe(URL refUrl) {
        if (refUrl == null || !this.getPath().equals(refUrl.getPath())) {
            return false;
        }

        if (!ObjectUtils.equals(protocol, refUrl.protocol)) {
            return false;
        }

        if (!StringUtils.equals(this.getParameter(URLParam.NODE_TYPE.getName()), CommonConstant.NODE_TYPE_SERVICE)) {
            return false;
        }

        String version = getParameter(URLParam.VERSION.getName(), URLParam.VERSION.getValue());
        String refVersion = refUrl.getParameter(URLParam.VERSION.getName(), URLParam.VERSION.getValue());
        if (!version.equals(refVersion)) {
            return false;
        }

        // 由于需要提供跨group访问rpc的能力，所以不再验证group是否一致。
        return true;
    }
    
    
    public String toFullStr() {
        StringBuilder builder = new StringBuilder();
        builder.append(getUri()).append("?");

        for (Map.Entry<String, String> entry : parameters.entrySet()) {
            String name = entry.getKey();
            String value = entry.getValue();

            builder.append(name).append("=").append(value).append("&");
        }

        return builder.toString();
    }
    


    public String getProtocol() {
		return protocol;
	}

	public void setProtocol(String protocol) {
		this.protocol = protocol;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getPath() {
		return path;
	}

	public void setPath(String path) {
		this.path = path;
	}

	public Map<String, String> getParameters() {
		return parameters;
	}

	public void setParameters(Map<String, String> parameters) {
		this.parameters = parameters;
	}

	public Map<String, Number> getNumbers() {
		return numbers;
	}

	public void setNumbers(Map<String, Number> numbers) {
		this.numbers = numbers;
	}
	
    public String getParameter(String name) {
        return parameters.get(name);
    }
	
	public String getParameter(String name, String defaultValue) {
        String value = getParameter(name);
        if (value == null) {
            return defaultValue;
        }
        return value;
    }
	
	public void addParameter(String name, String value) {
        if (StringUtils.isEmpty(name) || StringUtils.isEmpty(value)) {
            return;
        }
        parameters.put(name, value);
    }

    public void removeParameter(String name) {
        if (name != null) {
            parameters.remove(name);
        }
    }

    public void addParameters(Map<String, String> params) {
        parameters.putAll(params);
    }
	
	/**
     * 返回一个provider or caller的identity,如果两个url的identity相同，则表示相同的一个provider或者caller
     *
     * @return
     */
    public String getIdentity() {
        return protocol + PROTOCOL_SEPARATOR + host + ":" + port +
                "/" + getParameter(URLParam.GROUP.getName(), URLParam.GROUP.getValue()) + "/" +
                getPath() + "/" + getParameter(URLParam.VERSION.getName(), URLParam.VERSION.getValue()) +
                "/" + getParameter(URLParam.NODE_TYPE.getName(), URLParam.NODE_TYPE.getValue());
    }

	@SuppressWarnings("deprecation")
	@Override
    public int hashCode() {
        int factor = 31;
        int rs = 1;
        rs = factor * rs + ObjectUtils.hashCode(protocol);
        rs = factor * rs + ObjectUtils.hashCode(host);
        rs = factor * rs + ObjectUtils.hashCode(port);
        rs = factor * rs + ObjectUtils.hashCode(path);
        rs = factor * rs + ObjectUtils.hashCode(parameters);
        return rs;
    }

    @SuppressWarnings("deprecation")
	@Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof URL)) {
            return false;
        }
        URL ou = (URL) obj;
        if (!ObjectUtils.equals(this.protocol, ou.protocol)) {
            return false;
        }
        if (!ObjectUtils.equals(this.host, ou.host)) {
            return false;
        }
        if (!ObjectUtils.equals(this.port, ou.port)) {
            return false;
        }
        if (!ObjectUtils.equals(this.path, ou.path)) {
            return false;
        }
        return ObjectUtils.equals(this.parameters, ou.parameters);
    }
    
    public String toString() {
        return toSimpleString();
    }
    
    // 包含协议、host、port、path、group
    public String toSimpleString() {
        return getUri() + "?group=" + getGroup();
    }
    
    public String getUri() {
        return protocol + CommonConstant.PROTOCOL_SEPARATOR + host + ":" + port +
        		CommonConstant.PATH_SEPARATOR + path;
    }
    
    public String getGroup() {
        return getParameter(URLParam.GROUP.getName(), URLParam.GROUP.getValue());
    }
    
    public String getVersion() {
        return getParameter(URLParam.VERSION.getName(), URLParam.VERSION.getValue());
    }
    
    /**
     * comma separated host:port pairs, e.g. "127.0.0.1:3000"
     *
     * @return
     */
    public String getServerPortStr() {
        return buildHostPortStr(host, port);

    }
    
    public URL createCopy() {
        Map<String, String> params = new HashMap<String, String>();
        if (this.parameters != null) {
            params.putAll(this.parameters);
        }

        return new URL(protocol, host, port, path, params);
    }

    
    private static String buildHostPortStr(String host, int defaultPort) {
        if (defaultPort <= 0) {
            return host;
        }

        int idx = host.indexOf(":");
        if (idx < 0) {
            return host + ":" + defaultPort;
        }

        int port = Integer.parseInt(host.substring(idx + 1));
        if (port <= 0) {
            return host.substring(0, idx + 1) + defaultPort;
        }
        return host;
    }
    
    
    public Boolean getBooleanParameter(String name, boolean defaultValue) {
        String value = getParameter(name);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }

        return Boolean.parseBoolean(value);
    }
    
    
    public Integer getIntParameter(String name, int defaultValue) {
        Number n = getNumbers().get(name);
        if (n != null) {
            return n.intValue();
        }
        String value = parameters.get(name);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        int i = Integer.parseInt(value);
        getNumbers().put(name, i);
        return i;
    }
    
    public Boolean getMethodParameter(String methodName, String paramDesc, String name, boolean defaultValue) {
        String value = getMethodParameter(methodName, paramDesc, name);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        return Boolean.parseBoolean(value);
    }
    
    public Integer getMethodParameter(String methodName, String paramDesc, String name, int defaultValue) {
        String key = methodName + "(" + paramDesc + ")." + name;
        if(getNumbers() != null){
        	Number n = getNumbers().get(key);
            if (n != null) {
                return n.intValue();
            }
        }
        String value = getMethodParameter(methodName, paramDesc, name);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        int i = Integer.parseInt(value);
        getNumbers().put(key, i);
        return i;
    }
    
    public String getMethodParameter(String methodName, String paramDesc, String name) {
        String value = getParameter(CommonConstant.METHOD_CONFIG_PREFIX + methodName + "(" + paramDesc + ")." + name);
        if (value == null || value.length() == 0) {
            return getParameter(name);
        }
        return value;
    }

    public String getMethodParameter(String methodName, String paramDesc, String name, String defaultValue) {
        String value = getMethodParameter(methodName, paramDesc, name);
        if (value == null || value.length() == 0) {
            return defaultValue;
        }
        return value;
    }


	@Override
	public int compareTo(URL url) {
		int result = 0;
		if(url == null){
			return -1;
		}
		result = this.protocol.compareTo(url.getProtocol());
		if(result != 0){
			return result;
		}
		result = this.host.compareTo(url.getHost());
		if(result != 0){
			return result;
		}
		if(port != 0 && port != url.getPort()){
			result = this.port > url.getPort() ? 1 : -1;
			return result;
		}
		result = this.path.compareTo(url.getPath());
		if(result != 0){
			return result;
		}
		if(ObjectUtils.equals(parameters, url.getParameters())){
			return 0;
		}else{
			return ObjectUtils.hashCode(parameters) > ObjectUtils.hashCode(url.getParameters()) ? 1 : -1;
		}
	}



}
